package com.beiding.natago2.component;

import com.beiding.dbrequest.client.Client;
import com.beiding.dbrequest.client.ClientGroup;
import com.beiding.dbrequest.common.Mapping;
import com.beiding.dbrequest.common.Message;
import com.beiding.dbrequest.common.Request;
import com.beiding.dbrequest.common.Terminal;
import com.beiding.dbrequest.promise.Promise;
import com.beiding.natago2.data.ByteData;
import com.beiding.natago2.utils.DataParser;
import com.beiding.natago2.utils.MyPrint;
import io.netty.bootstrap.Bootstrap;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioSocketChannel;
import lombok.extern.java.Log;

import java.util.concurrent.ExecutorService;

/**
 * 客户机接口
 * 集成自公共接口
 */
@Log
@Mapping("/client")
public class ClientExecutor extends CsCommon {


    /**
     * 客户端组
     */
    private ClientGroup clientGroup;

    /**
     * 目标客户机
     */
    private String targetHost;

    /**
     * 目标端口
     */
    private Integer targetPort;

    public void init() {
        clientGroup.each(client -> {
            client.getDispatcher().addController(this);});
        log.info("初始化完成 host:" + targetHost + " port:" + targetPort);
    }


    /**
     * 创建客户机接口
     *
     * @param request
     * @return
     */
    @Mapping("/createTarget")
    public Promise createTarget(Request request) {
        //创建一个目标连接
        Long id = (Long) request.getBody();
        Target target = new Target(id, request.getCtx());
        log.info("开始创建连接:" + id);
        return target.start();
    }

    private NioEventLoopGroup workerGroup = new NioEventLoopGroup();

    /**
     * 目标客户机,内部类
     */
    public class Target {

        private Long id;

        /**
         * 内部连接通道
         */
        ChannelHandlerContext ctx0;

        public Target(Long id, ChannelHandlerContext ctx0) {
            this.id = id;
            this.ctx0 = ctx0;
        }

        /**
         * 启动目标连接
         *
         * @return
         */
        public Promise start() {
            final Promise promise = new Promise();
            Bootstrap bootstrap = new Bootstrap();
            bootstrap.group(workerGroup)
                    .channel(NioSocketChannel.class)
                    .handler(new ChannelInitializer<SocketChannel>() {
                        @Override
                        protected void initChannel(SocketChannel ch) {
                            ch.pipeline()
                                    .addLast(new ByteArrayEncoder())
                                    .addLast(new ByteArrayDecoder())
                                    .addLast(new H() {
                                        @Override
                                        public void channelActive(ChannelHandlerContext ctx) throws Exception {
                                            channelStore.setId(ctx, id);
                                            channelStore.put(ctx);
                                            log.info("创建连接成功完成:" + id);
                                            promise.doOk(id);
                                        }
                                    })
                            ;

                        }
                    });

            //绑定端口
            bootstrap.connect(targetHost, targetPort).addListener(future -> {
                if (!future.isSuccess()) {
                    promise.doError(future.cause());
                }
            });

            return promise;
        }

        /**
         * 数据读取封装
         */
        public class H extends ChannelInboundHandlerAdapter {

            private ExecutorService executorService = ExecutorPool.getOne();

            /**
             * 客户机外部通道读取到数据时
             *
             * @param ctx
             * @param msg
             * @throws Exception
             */
            @Override
            public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
                executorService.submit(() -> {

                    if (getDebugger()) {
                        byte[] data = (byte[]) msg;
                        printData("<<<===", data, "-------");
                    }

                    //获取内部通道id
                    Long id = channelStore.getId(ctx);
                    //首先检查一下通道是否已经被关闭
                    if (!channelStore.isActive(id)) {
                        log.info("通道已被关闭,数据忽略");
                        return;
                    }

                    //封装数据
                    ByteData byteData = new ByteData();
                    byteData.setId(id);
                    byteData.setData((byte[]) msg);
                    try {
                        Terminal.broadcastWithChannel(ctx0, Message.request("/server/onData", byteData));
                    } catch (Exception e) {
                        log.info("发送目标数据失败 主动关闭通道" + id);
                        channelStore.close(ctx);
                        e.printStackTrace();
                    }
                });
            }

            /**
             * 通道被关闭
             *
             * @param ctx
             * @throws Exception
             */
            @Override
            public void channelInactive(ChannelHandlerContext ctx) throws Exception {
                Long id = channelStore.getId(ctx);
                if (channelStore.isActive(id)) {
                    executorService.submit(() -> {
                        channelStore.remove(id);
                        log.info("命令:服务端关闭目标连接" + id);
                        //通知关闭服务,不能使用随机通道发送该消息,会导致先关闭通道后收到消息的情况,导致最后的消息不能正常写入完成
                        //发起关闭通知的内部通道必须是与当前目标通道绑定的
                        Terminal.requestWithChannel(ctx0, "/server/close", id).syncR();
                        log.info("命令:服务端关闭目标连接-完成" + id);
                    });
                }

            }

            @Override
            public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
                cause.printStackTrace();
            }
        }

    }

    public void setTargetHost(String targetHost) {
        this.targetHost = targetHost;
    }

    public void setTargetPort(Integer targetPort) {
        this.targetPort = targetPort;
    }

    public void setClientGroup(ClientGroup clientGroup) {
        this.clientGroup = clientGroup;
    }

}
